#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_sntp.h>
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_log.h"
#include "esp_event.h"
#include "protocol_examples_common.h"

#include "vernon_max7219.h"
#include "bh1750.h"
#include "ds3231.h"

#define BH1750_TAG "BH1750"

#define I2C_MASTER_SCL_IO 22      /*!< gpio number for I2C master clock */
#define I2C_MASTER_SDA_IO 21      /*!< gpio number for I2C master data  */
#define I2C_MASTER_NUM I2C_NUM_0  /*!< I2C port number for master dev */

#define I2C_MASTER_DS3231_SCL 26
#define I2C_MASTER_DS3231_SDA 27
#define I2C_MASTER_DS3231_NUM I2C_NUM_1

#define MAX7219_CASCADE_SIZE 3
#define MAX7219_EXTRA_CASCADE_SIZE 4

// 点阵缓存矩阵
static uint8_t matrix_buffer[8][MAX7219_CASCADE_SIZE]; //串行传输，所以一定是八行，但是列是不一定的
static uint8_t matrix_buffer_extra[8][MAX7219_EXTRA_CASCADE_SIZE];

static ds3231_dev_t * ds3231_handle = NULL;
static nvs_handle_t ntp_test_nvs_handle;

static vernon_max7219_t vernon_max7219_handle = {
    .mosi_pin = 13,
    .cs_pin = 15,
    .clk_pin = 14,
    .cascade_size = MAX7219_CASCADE_SIZE,
    .used_spi_id = SPI2_HOST,
    .matrix_buffer = matrix_buffer,
    .left_source = true,
    .is_anode = false
};

static vernon_max7219_t vernon_max7219_handle_extra = {
    .mosi_pin = 23,
    .cs_pin = 5,
    .clk_pin = 18,
    .cascade_size = MAX7219_EXTRA_CASCADE_SIZE,
    .used_spi_id = SPI3_HOST,
    .matrix_buffer = matrix_buffer_extra,
    .left_source = false,
    .is_anode = true
};

static i2c_master_bus_handle_t bus_handle; // I2C总线句柄
static void i2c_bus_0_init()
{
    i2c_master_bus_config_t bus_config = {
        .i2c_port = I2C_MASTER_NUM,
        .sda_io_num = I2C_MASTER_SDA_IO,
        .scl_io_num = I2C_MASTER_SCL_IO,
        .clk_source = I2C_CLK_SRC_DEFAULT,
        .glitch_ignore_cnt = 7,
        .flags.enable_internal_pullup = true,
    };
    ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &bus_handle));
}

static i2c_master_bus_handle_t bus_1_handle; // I2C总线句柄
static void i2c_bus_1_init()
{
    i2c_master_bus_config_t bus_config = {
        .i2c_port = I2C_MASTER_DS3231_NUM,
        .sda_io_num = I2C_MASTER_DS3231_SDA,
        .scl_io_num = I2C_MASTER_DS3231_SCL,
        .clk_source = I2C_CLK_SRC_DEFAULT,
        .glitch_ignore_cnt = 7,
        .flags.enable_internal_pullup = true,
    };
    ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &bus_1_handle));
}

//范围映射
static inline int map(int x, int in_min, int in_max, int out_min, int out_max)
{
    return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}   

// NTP Sync Time
static const char *TAG = "NTPServer";
#define NTP_SERVER "ntp.aliyun.com"
void time_sync_notification_cb(struct timeval *tv)
{
	ESP_LOGI(TAG, "Notification of a time synchronization event");
}

static inline void initialize_sntp(void)
{
	ESP_LOGI(TAG, "Initializing SNTP");
	esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
	//sntp_setservername(0, "pool.ntp.org");
	ESP_LOGI(TAG, "Your NTP Server is %s", NTP_SERVER);
	esp_sntp_setservername(0, NTP_SERVER);
	esp_sntp_set_time_sync_notification_cb(time_sync_notification_cb);
	esp_sntp_init();
}

static inline bool config_ntp_time(void)
{
	ESP_ERROR_CHECK( nvs_flash_init() );
	ESP_ERROR_CHECK( esp_netif_init() );
	ESP_ERROR_CHECK( esp_event_loop_create_default());

	/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
	 * Read "Establishing Wi-Fi or Ethernet Connection" section in
	 * examples/protocols/README.md for more information about this function.
	 */
	ESP_ERROR_CHECK(example_connect());

	initialize_sntp();

	// wait for time to be set
	int retry = 0;
	const int retry_count = 20;
	while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET && ++retry < retry_count) {
		ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
		vTaskDelay(2000 / portTICK_PERIOD_MS);
	}

	ESP_ERROR_CHECK( example_disconnect() );
	if (retry == retry_count) return false;
	return true;
}

static inline void init_screen_info()
{
    int8_t temp;
    struct tm timeinfo;
    ds3231_get_time(ds3231_handle, &timeinfo);
    ds3231_get_temp_integer(ds3231_handle, &temp);

    vernon_max7219_draw_buffer(&vernon_max7219_handle, 0, 0, (timeinfo.tm_hour / 10) % 10);
    vernon_max7219_draw_buffer(&vernon_max7219_handle, 0, 5, timeinfo.tm_hour % 10);
    vernon_max7219_draw_buffer(&vernon_max7219_handle, 0, 10, 10); // 给buffer缓存区写两个冒号
    vernon_max7219_draw_buffer(&vernon_max7219_handle, 0, 11, 10); // 就是小时和分钟中间的两个冒号
    vernon_max7219_draw_buffer(&vernon_max7219_handle, 0, 13, (timeinfo.tm_min / 10) % 10);
    vernon_max7219_draw_buffer(&vernon_max7219_handle, 0, 18,  timeinfo.tm_min % 10);

    vernon_max7219_draw_buffer(&vernon_max7219_handle_extra, 0, 0, ((timeinfo.tm_mon + 1) / 10) % 10);
    vernon_max7219_draw_buffer(&vernon_max7219_handle_extra, 0, 5, (timeinfo.tm_mon + 1) % 10);
    vernon_max7219_draw_buffer(&vernon_max7219_handle_extra, 0, 10, 11);
    vernon_max7219_draw_buffer(&vernon_max7219_handle_extra, 0, 13, (timeinfo.tm_mday / 10) % 10);
    vernon_max7219_draw_buffer(&vernon_max7219_handle_extra, 0, 18,  timeinfo.tm_mday % 10);
    vernon_max7219_draw_buffer(&vernon_max7219_handle_extra, 0, 23, (temp / 10) % 10);
    vernon_max7219_draw_buffer(&vernon_max7219_handle_extra, 0, 28,  temp % 10);

    vernon_max7219_send_buffer(&vernon_max7219_handle);
    vernon_max7219_send_buffer(&vernon_max7219_handle_extra);
}

#define TIMEZONE 8
void set_clock(void * para)
{
    ESP_LOGI(pcTaskGetName(0), "Connecting to WiFi and getting time over NTP.");
	if(!config_ntp_time()) {
		ESP_LOGE(pcTaskGetName(0), "Fail to getting time over NTP.");
		while (1) { vTaskDelay(1); }
	}

	// update 'now' variable with current time
	time_t now;
	struct tm timeinfo;
	char strftime_buf[64];

    while(1)
    {
        time(&now);
        now = now + (TIMEZONE*60*60);
        localtime_r(&now, &timeinfo);
        strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
        ESP_LOGI(pcTaskGetName(0), "The current date/time is: %s", strftime_buf);

        ESP_LOGD(pcTaskGetName(0), "timeinfo.tm_sec=%d",timeinfo.tm_sec);
        ESP_LOGD(pcTaskGetName(0), "timeinfo.tm_min=%d",timeinfo.tm_min);
        ESP_LOGD(pcTaskGetName(0), "timeinfo.tm_hour=%d",timeinfo.tm_hour);
        ESP_LOGD(pcTaskGetName(0), "timeinfo.tm_wday=%d",timeinfo.tm_wday);
        ESP_LOGD(pcTaskGetName(0), "timeinfo.tm_mday=%d",timeinfo.tm_mday);
        ESP_LOGD(pcTaskGetName(0), "timeinfo.tm_mon=%d",timeinfo.tm_mon);
        ESP_LOGD(pcTaskGetName(0), "timeinfo.tm_year=%d",timeinfo.tm_year);

        // need to add 1900 in hour from ntp client(struct tm)
        timeinfo.tm_year += 1900;
        if (ds3231_set_time(ds3231_handle, &timeinfo) != ESP_OK) {
		    ESP_LOGE(pcTaskGetName(0), "Could not set time.");
		    while (1) { vTaskDelay(1); }
	    }else ESP_LOGI(pcTaskGetName(0), "Set Clock From NTP OK!");

        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
    }
}

void update_display(void * para)
{
    static uint8_t hours, minutes, month, day, temperature;
    // Init max7219
    vernon_max7219_init(&vernon_max7219_handle);
    vernon_max7219_init(&vernon_max7219_handle_extra);
    vernon_max7219_set_brightness(&vernon_max7219_handle, 3);
    vernon_max7219_set_brightness(&vernon_max7219_handle_extra, 3);

    // Init ds3231
    ds3231_handle = ds3231_init(bus_1_handle, DS3231_ADDR);
    struct tm timeinfo;
    static int8_t temp;

    // Initialize NVS
    static const char *NTP_TEST_COUNT_TAG = "NTP_TEST_COUNT";
    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        // NVS partition was truncated and needs to be erased
        // Retry nvs_flash_init
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    ESP_ERROR_CHECK( err );

    init_screen_info(); // 初始化显示一次

    while(1)
    {
        ds3231_get_time(ds3231_handle, &timeinfo);
        ds3231_get_temp_integer(ds3231_handle, &temp);

        if(hours != timeinfo.tm_hour)
        {
            hours = timeinfo.tm_hour;
            vernon_max7219_draw_buffer(&vernon_max7219_handle, 0, 0, (timeinfo.tm_hour / 10) % 10);
            vernon_max7219_draw_buffer(&vernon_max7219_handle, 0, 5, timeinfo.tm_hour % 10);
            vernon_max7219_draw_buffer(&vernon_max7219_handle, 0, 10, 10); // 给buffer缓存区写两个冒号
            vernon_max7219_draw_buffer(&vernon_max7219_handle, 0, 11, 10); // 就是小时和分钟中间的两个冒号
            vernon_max7219_send_buffer(&vernon_max7219_handle);
        }
        if(minutes != timeinfo.tm_min){
            minutes = timeinfo.tm_min;
            vernon_max7219_draw_buffer(&vernon_max7219_handle, 0, 13, (timeinfo.tm_min / 10) % 10);
            vernon_max7219_draw_buffer(&vernon_max7219_handle, 0, 18,  timeinfo.tm_min % 10);
            vernon_max7219_send_buffer(&vernon_max7219_handle);

            vernon_max7219_send_buffer(&vernon_max7219_handle_extra);
        }
        if(month != (timeinfo.tm_mon + 1))
        {
            month = timeinfo.tm_mon + 1;
            vernon_max7219_draw_buffer(&vernon_max7219_handle_extra, 0, 0, ((timeinfo.tm_mon + 1) / 10) % 10);
            vernon_max7219_draw_buffer(&vernon_max7219_handle_extra, 0, 5, (timeinfo.tm_mon + 1) % 10);
            vernon_max7219_draw_buffer(&vernon_max7219_handle_extra, 0, 10, 11);

            vernon_max7219_send_buffer(&vernon_max7219_handle_extra);
        }
        if(day != timeinfo.tm_mday)
        {
            day = timeinfo.tm_mday;
            vernon_max7219_draw_buffer(&vernon_max7219_handle_extra, 0, 13, (timeinfo.tm_mday / 10) % 10);
            vernon_max7219_draw_buffer(&vernon_max7219_handle_extra, 0, 18,  timeinfo.tm_mday % 10);

            vernon_max7219_send_buffer(&vernon_max7219_handle_extra);
        }
        if(temperature != temp)
        {
            temperature = temp;
            vernon_max7219_draw_buffer(&vernon_max7219_handle_extra, 0, 23, (temp / 10) % 10);
            vernon_max7219_draw_buffer(&vernon_max7219_handle_extra, 0, 28,  temp % 10);

            vernon_max7219_send_buffer(&vernon_max7219_handle_extra);
        }
        if(month != (timeinfo.tm_mon + 1))
        {
            month = timeinfo.tm_mon + 1;
            // Write the log
            {            
                uint32_t command_counter = 0;
                // Open nvs
                err = nvs_open("ntp_test", NVS_READWRITE, &ntp_test_nvs_handle);
                if (err != ESP_OK) {
                    ESP_LOGI(NTP_TEST_COUNT_TAG, "Error (%s) opening NVS handle!", esp_err_to_name(err));
                } else ESP_LOGI(NTP_TEST_COUNT_TAG, "NTP_TEST_NVS Open Done");
                //Read
                err = nvs_get_u32(ntp_test_nvs_handle, "ntp_cmd_count", &command_counter);
                switch (err) {
                    case ESP_OK:
                        ESP_LOGI(NTP_TEST_COUNT_TAG, "Command counter = %" PRIu32 "", command_counter);
                        break;
                    case ESP_ERR_NVS_NOT_FOUND:
                        ESP_LOGI(NTP_TEST_COUNT_TAG, "The value is not initialized yet!");
                        break;
                    default :
                        ESP_LOGI(NTP_TEST_COUNT_TAG, "Error (%s) reading!", esp_err_to_name(err));
                }
                ESP_ERROR_CHECK(nvs_set_u32(ntp_test_nvs_handle, "ntp_cmd_count", ++command_counter));

                ESP_LOGI(NTP_TEST_COUNT_TAG, "Committing updates in NVS ... ");
                ESP_ERROR_CHECK(nvs_commit(ntp_test_nvs_handle));
                // Close
                nvs_close(ntp_test_nvs_handle);
            }
            // Wait for norify update ntp time every month
            TaskHandle_t set_clock_task = (TaskHandle_t)para;
            xTaskNotifyGive(set_clock_task);
        }
        
        // do not use the strftime function, because it will add 1900 automatically 
        ESP_LOGI(pcTaskGetName(0), "%04d-%02d-%02d %02d:%02d:%02d, %d deg Cel", 
			timeinfo.tm_year, timeinfo.tm_mon + 1, timeinfo.tm_mday, 
            timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec, temp);

        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void bh1750_task()
{
    bh1750_handle_t bh1750 = bh1750_create(bus_handle, BH1750_I2C_ADDRESS_DEFAULT);

    bh1750_measure_mode_t cmd_measure;
    float bh1750_data;

    // power bh1750
    ESP_ERROR_CHECK(bh1750_power_on(bh1750)); 

    // continous mode
    cmd_measure = BH1750_CONTINUE_4LX_RES;
    ESP_ERROR_CHECK(bh1750_set_measure_mode(bh1750, cmd_measure));
    vTaskDelay(30 / portTICK_PERIOD_MS);

    while(1)
    {
        ESP_ERROR_CHECK(bh1750_get_data(bh1750, &bh1750_data));
        ESP_LOGI(BH1750_TAG, "bh1750 val(continuously mode): %f", bh1750_data);
        int map_value = map((int)bh1750_data, 0, 350, 0, 15);
        vernon_max7219_set_brightness(&vernon_max7219_handle, map_value);
        vernon_max7219_set_brightness(&vernon_max7219_handle_extra, map_value);
        vTaskDelay(pdMS_TO_TICKS(3000));
    }
}

void app_main()
{
    i2c_bus_0_init();
    i2c_bus_1_init();

    TaskHandle_t set_clock_task = NULL;
    xTaskCreate(set_clock, "set_clock", configMINIMAL_STACK_SIZE * 3, NULL, 5, &set_clock_task);
    xTaskCreate(update_display, "update_display", configMINIMAL_STACK_SIZE * 5, (void *)set_clock_task, 6, NULL);
    xTaskCreate(bh1750_task, "bh1750_task", configMINIMAL_STACK_SIZE * 3, NULL, 1, NULL);
}